#include "crane_jpuc_common.h"

#if USE_CRANE_CAMERA && USE_CRANE_JPU
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#include "lv_drivers/camera/cam_util.h"
#include "lv_drivers/common/utils.h"
#include "lv_drivers/common/media_clock.h"
#include "../../board.h"
#include "crane_jpuc_encoder.h"
#include "crane_jpuc_table.h"
#include "lv_drivers/camera/camera_common_log.h"

/* close printf */
//#undef printf
//#define printf(fmt, args...) do {} while(0)

/*
 * #define HUFF_TAB_DUMP 0
 * #define Q_TAB_DUMP 0
 * #define JPEG_HEADER_DUMP 0
 */
#define BU_REG_RDMDFYWR(x,y)  (BU_REG_WRITE(x,((BU_REG_READ(x))|(y))))
#define BU_REG_RDCLEAR(x,y)  (BU_REG_WRITE(x,((BU_REG_READ(x))&~(y))))
#define MASK_(__n,__w)  (((1UL<<(__w))-1)<<(__n))

static void jpeg_write_ahb32(uint32_t offset, uint32_t data)
{
    jpu_write(offset, data);
#ifdef JPEG_REG_DUMP
    printf("jpeg w:0x%08x = 0x%08x\r\n", JPEG_BASE_ADDR + offset, data);
#endif
}

static uint32_t jpeg_read_ahb32(uint32_t offset)
{
    uint32_t val = 0xBABABABA;
    val = jpu_read(offset);
#ifdef JPEG_REG_DUMP
    printf("jpeg r:0x%08x = 0x%08x\r\n", JPEG_BASE_ADDR+offset, val);
#endif

    return val;
}

static void JpgEncHuffTabSetUp(void)
{
    int i, val;
    const uint8_t *hufftab = jpgEncHuffTable[JPEG_QUALITY_LOW].data;

    jpeg_write_ahb32(MJPEG_HUFF_CTRL_REG, 0x03);
    for ( i = 0; i < 544; i++ ) {
        val = (hufftab[3*i] << 16) +
            (hufftab[3*i+1] << 8) +
            (hufftab[3*i+2]);
        val &= MASK_(0,20);
#ifdef HUFF_TAB_DUMP
        printf("<0x%08x>[huff-%d-0x%08x]\n",JPEG_BASE_ADDR+MJPEG_HUFF_DATA_REG, i, val);
#endif
        jpu_write(MJPEG_HUFF_DATA_REG, val);
    }
    jpeg_write_ahb32(MJPEG_HUFF_CTRL_REG, 0x00);
}

static void JpgEncQMatTabSetUp(void)
{
    int i, val, quant_val;
    const uint8_t *qtab = jpgEncQTable[JPEG_QUALITY_LOW].data;

    jpeg_write_ahb32(MJPEG_QMAT_CTRL_REG, 0x03);
    for ( i = 0; i < 64; i++ ) {
        val = (qtab[4*i] << 16) +
            (qtab[4*i+1] << 8) +
            qtab[4*i+2];
        val &= MASK_(0,20);
        quant_val = qtab[4*i+3];
        quant_val &= MASK_(0,8);
#ifdef Q_TAB_DUMP
        printf("<0x%08x>[qtbl-%d-0x%08x]\n",JPEG_BASE_ADDR+MJPEG_QMAT_DATA_REG, i, (quant_val<<20) + val);
#endif
        jpu_write(MJPEG_QMAT_DATA_REG, (quant_val<<20) + val);
    }
    jpeg_write_ahb32(MJPEG_QMAT_CTRL_REG, 0x00);

    jpeg_write_ahb32(MJPEG_QMAT_CTRL_REG, 0x43);
    for ( i = 0; i < 64; i++ ) {
        val = (qtab[256+4*i] << 16) + (qtab[256+4*i+1] << 8) + qtab[256+4*i+2];
        val &= MASK_(0,20);
        quant_val = qtab[256+4*i+3];
        quant_val &= MASK_(0,8);
#ifdef Q_TAB_DUMP
        printf("<0x%08x>[qtbl-%d-0x%08x]\n",JPEG_BASE_ADDR+MJPEG_QMAT_DATA_REG, i, (quant_val<<20) + val);
#endif
        jpu_write(MJPEG_QMAT_DATA_REG, (quant_val<<20) + val);
    }
    jpeg_write_ahb32(MJPEG_QMAT_CTRL_REG, 0x40);

    jpeg_write_ahb32(MJPEG_QMAT_CTRL_REG, 0x83);
    for ( i = 0; i < 64; i++ ) {
        val = (qtab[512+4*i] << 16) +
            (qtab[512+4*i+1] << 8) +
            qtab[512+4*i+2];
        val &= MASK_(0,20);
        quant_val = qtab[512+4*i+3];
        quant_val &= MASK_(0,8);
#ifdef Q_TAB_DUMP
        printf("<0x%08x>[qtbl-%d-0x%08x]\n",JPEG_BASE_ADDR+MJPEG_QMAT_DATA_REG, i, (quant_val<<20) + val);
#endif
        jpu_write(MJPEG_QMAT_DATA_REG, (quant_val<<20) + val);
    }
    jpeg_write_ahb32(MJPEG_QMAT_CTRL_REG, 0x80);
}

static void JpgEncHeaderSetup(int w, int h)
{
    const unsigned char *puchar = jpgEncHeaderTable[JPEG_QUALITY_LOW].data;
    uint32_t length = ((*puchar << 8) + (*(puchar + 1))); //jpgEncHeaderTable[JPEG_QUALITY_SUPERFINE].length;
    uint8_t height_h, height_l, width_h, width_l;
    int i, j;
    int write_cnt = 0;

    if (w < 0xFFFF && h < 0xFFFF) {
        height_h = (h & 0xFF00) >> 8;
        height_l = (h & 0x00FF);
        width_h = (w & 0xFF00) >> 8;
        width_l = (w & 0x00FF);
    } else {
        printf("\n\n\n!!!!!!NO HEADER FOR %dx%d!!!!\n\n\n", w, h);
    }

    CAMLOGD("jpeg header length = %d", length);

    for ( i = 0; i < length; i++ ) {
        if ( i % 4 == 3 ) {
            int val = 0x01;
            uint32_t data;
            while ( val == 0x01 ) {
                val = jpu_read(MJPEG_GBU_PBIT_BUSY_REG);
            }

            data = (*(puchar+2+i-3)<<24) + (*(puchar+2+i-2)<<16) + (*(puchar+2+i-1)<<8) + *(puchar+2+i);

            if ( write_cnt == 1 ) {
                uint32_t fn = 1;
                data = (data & MASK_(16,16)) + (fn & MASK_(0,16));
            }
        /* setup jpeg header with image size */
            if ( write_cnt == 145) {
                uint32_t fn = height_h;
                data = (data & MASK_(8,24)) + (fn & MASK_(0,8));
            }
            if ( write_cnt == 146 ) {
                uint32_t fn = (height_l << 24) + (width_h << 16) + (width_l << 8);
                data = (data & MASK_(0,8)) + (fn & MASK_(8,24));
            }
            write_cnt++;

#ifdef JPEG_HEADER_DUMP
            printf("<0x%08x>[jheader-%d-0x%08x]\n", JPEG_BASE_ADDR + MJPEG_GBU_PBIT_32_REG, i-3, data);
#endif
            jpu_write(MJPEG_GBU_PBIT_32_REG, data);

            if ( i+4 >= length ) {
                for ( j = i+1; j < length; j++ ) {
                    uint32_t data = (uint32_t)(*(puchar+2+j));
#ifdef JPEG_HEADER_DUMP
                    printf("<0x%08x>[jheaderemainder-%d-0x%08x]\n", JPEG_BASE_ADDR + MJPEG_GBU_PBIT_08_REG, j, data);
#endif
                    jpu_write(MJPEG_GBU_PBIT_08_REG, data);
                }
                break;
            }
        }
    }
}

static void InitNieuport(void)
{
    int val = 0x02;

    jpeg_write_ahb32(MJPEG_PIC_START_REG, val);
    while ( val == 0x02 ) {
        val = 0x02 & jpu_read(MJPEG_PIC_START_REG);
    }
    CAMLOGV("InitNieuport:0x%08x", jpu_read(MJPEG_PIC_STATUS_REG));

    return;
}

static void SetOutputAddr(uint32_t out_addr, uint32_t in_w, uint32_t in_h)
{
    jpeg_write_ahb32(SD_MJPEG_BBC_WR_PTR_REG, out_addr);
    jpeg_write_ahb32(SD_MJPEG_BBC_BAS_ADDR_REG, out_addr);
    jpeg_write_ahb32(SD_MJPEG_BBC_END_ADDR_REG, out_addr + in_w * in_h);

    jpeg_write_ahb32(MJPEG_BBC_WR_PTR_REG, out_addr);
    jpeg_write_ahb32(MJPEG_BBC_BAS_ADDR_REG, out_addr);
    jpeg_write_ahb32(MJPEG_BBC_END_ADDR_REG, out_addr + in_w * in_h);

    CAMLOGV("set encoding output addr 0x%x", out_addr);

    return;
}

static void SetInputSize(int w, int h)
{
    int val = ((w & MASK_(0, 16)) << 16) + (h & MASK_(0, 16));

    jpeg_write_ahb32(SD_MJPEG_PIC_SIZE_REG, val);
    jpeg_write_ahb32(MJPEG_PIC_SIZE_REG, val);

    CAMLOGV("set jpeg encoding size %dx%d", w, h);

    return;
}

static void EnableContinuousEncode(int on)
{
    int val = on ? 0x03 : 0x02;

    jpeg_write_ahb32(SD_MJPEG_PIC_CON_ENC_REG, val);//FIXME: write once only

    if ( on ) {
        uint32_t rdata;

        rdata = jpu_read(MJPEG_PIC_CTRL_REG);
        rdata |= BIT_31;
        jpeg_write_ahb32(MJPEG_PIC_CTRL_REG, rdata);
    }
}

int jpu_cam_enccfg(uint32_t in_w, uint32_t in_h, uint32_t out_addr)
{
    InitNieuport();

    // shadow enable, tc_dir, encode, dummpy, op_mode // fixed
    jpeg_write_ahb32(MJPEG_PIC_CTRL_REG, BIT(5) + BIT(4) + BIT(3));   //FIXME: write once only
    JpgEncHuffTabSetUp();
    JpgEncQMatTabSetUp();

    SetOutputAddr(out_addr, in_w, in_h);
    SetInputSize(in_w, in_h);
    EnableContinuousEncode(0);
    jpeg_write_ahb32(MJPEG_PIC_START_REG, 1);

    JpgEncHeaderSetup(in_w, in_h);   // generate jpeg header by software

    return 0;
}

/**
 * @brief In order to start a new encoder operation, applications must
 *     open a new instance for this encoder operation.
 *
 * @return
 */
int jpu_cam_encopen(void)
{
    int ret = 0;

    if (jpu_dev.jpu_open_cnt == 0) {
        jpu_dev.state = JPU_STATE_ENC_ACTIVE;
        jpu_dev.jpu_open_cnt++;

        media_clk_jpu_on();
        jpu_irq_enable();
//        jpu_clk_reset();

        /* offline dma jpeg hardmux set */
        BU_REG_RDMDFYWR(0xD420C2B0, BIT_1); /* jpeg_encoder '1' encoder '0' decoder */
        BU_REG_RDMDFYWR(0xD420C2B0, BIT_2); /* jpeg_offline '1' offline '0' online */
//        printf("jpu_cam_encopen 0x2b0 = 0x%08x \n\r", BU_REG_READ(0xD420C2B0));
    } else {
        CAMLOGE("JPU is busy!");
        ret = -1;
    }

    return ret;
}

/**
 * @brief wait encoding operations finish
 *
 * @return  jpeg size
 */
uint32_t jpu_cam_encfinish(void)
{
    int rc = 0;
    uint32_t actual_flags = 0;
    uint32_t end_ptr = 0;

    rc = uos_wait_flag(jpeg_encode_flag, MJPEG_HW_IRQ_STATUS_CODEC_DONE,
                UOS_FLAG_OR_CLEAR, &actual_flags, JPU_TIMEOUT);
    if ((UOS_SUCCESS == rc) && (actual_flags & MJPEG_HW_IRQ_STATUS_CODEC_DONE)) {
        CAMLOGD("jpu hardware encoding Done!");
    } else {
        CAMLOGE("jpu hardware encoding Failed!!!!!");
    }
    end_ptr = jpeg_read_ahb32(LAST_MJPEG_BBC_WR_PTR_REG);

    return end_ptr;
}

/**
 * @brief When an application finished encoding operations and wanted
 *     to release this instance for other processing, the application
 *     should close this instance by calling this function.
 *
 * @return
 */
int jpu_cam_encclose(void)
{
    if (jpu_dev.jpu_open_cnt == 0) {
        return 0;
    }

    /* offline dma jpeg hardmux set */
    BU_REG_RDCLEAR(0xD420C2B0, BIT_1); /* jpeg_encoder '1' encoder '0' decoder */
    BU_REG_RDCLEAR(0xD420C2B0, BIT_2); /* jpeg_offline '1' offline '0' online */
//    printf("jpu_cam_encclose 0x2b0 = 0x%08x \n\r", BU_REG_READ(0xD420C2B0));

    jpu_irq_disable();
    media_clk_jpu_off();
    jpu_dev.state = JPU_STATE_OFF;
    jpu_dev.jpu_open_cnt--;

    return 0;
}

#endif

